#!/usr/bin/env python

# Copyright 2013 Diego Essaya
# 
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
# 
#        http://www.apache.org/licenses/LICENSE-2.0
# 
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.

import sys
import operator

class Templates:
	header = """<html>
		<head>
		<meta charset="utf-8">
		<title>Disk usage</title>
		<style type="text/css">
			div {
				position: absolute;
			}
			.block {
				background-color: #ffffff;
				border-color: black;
				border-style: solid;
				border-width: 1px;
				overflow: hidden;
			}
			.block:hover {
				border-color: #ff0000;
				z-index: 1000;
			}
			.block > span {
				display: inline-block;
				top: 50%;
				position: absolute;
				margin-top: -1ex;
			}
			.size {
				color: #8080ff;
			}
			html, body, #root {
				height: 100%;
				width: 100%;
				overflow:hidden;
				font-family: sans-serif;
			}
			#root {
				background-color: #a0a0a0;
			}
			* {
				padding: 0;
				margin: 0;
			}
		</style>
		<script type="text/javascript">
			var root = 
	"""

	node_header = "{'name':'%(name)s','size':%(size)d,'children':[\n"
	node_footer = "]}"

	footer = """
			;

			if (!String.prototype.format) {
			  String.prototype.format = function() {
				var args = arguments;
				return this.replace(/{(\d+)}/g, function(match, number) { 
				  return typeof args[number] != 'undefined'
					? args[number]
					: match
				  ;
				});
			  };
			}

			function humanReadableBytes(bytes) {
				var unit = 1024;
				if (bytes < unit) return bytes + " KiB";
				var exp = Math.floor(Math.log(bytes) / Math.log(unit));
				var pre = "MGTPE".charAt(exp - 1) + "i";
				return "{0} {1}B".format((bytes / Math.pow(unit, exp)).toFixed(1), pre);
			}

			function GUID() {
				var S4 = function() {
				   return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
				};
				return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
			}

			var COLS = 5;
			var colWidth = 100.0 / COLS;

			function left_percent(depth)
			{
				return depth * 100 / COLS;
			}

			function top_percent(parent, size_before)
			{
				if (parent == null)
					return 0;
				return parent.top_percent + (size_before * parent.height_percent) / parent.size;
			}

			function height_percent(node, parent)
			{
				if (parent == null)
					return 100;
				return (node.size * parent.height_percent) / parent.size;
			}

			var targets = {};

			function node_clicked(guid)
			{
				var node = targets[guid];
				if (node.depth == 0)
				{
					if (node.parent != undefined)
						init(node.parent);
				}
				else
				{
					init(node);
				}
			}
			
			function generate_div(node, parent, depth, size_before)
			{
				if (node.guid == undefined)
				{
					node.guid = GUID();
					node.parent = parent;
					targets[node.guid] = node;
				}
				
				node.depth = depth;
				node.left_percent = left_percent(depth);
				node.top_percent = top_percent(parent, size_before);
				node.height_percent = height_percent(node, parent);
				
				var s = "";
				
				if (depth >= 0)
				{
					s += '<div onclick="node_clicked(\\'{6}\\')" class="block" style="top:{0}%; left:{1}%; height:{2}%; width:{3}%"><span>{4} <span class="size">{5}</span></span>'.format(
						node.top_percent,
						node.left_percent,
						node.height_percent,
						colWidth,
						node.name,
						humanReadableBytes(node.size),
						node.guid
					);
					s += "</div>";
				}
				
				if (depth < COLS - 1)
				{
					var n = node.children.length;
					var accum = 0;
					for (var i = 0; i < node.children.length; i++)
					{
						var child = node.children[i];
						s += generate_div(child, node, depth + 1, accum);
						accum += child.size;
					}
				}
				
				return s;
			}

			function init(node)
			{
				document.getElementById('root').innerHTML = generate_div(node, null, node == root ? -1 : 0, 0);
			}
		</script>
		</head>
		<body onload="init(root)">
			<div id="root">
			</div>
		</body>
		</html>
	"""

class Node:
	def __init__(self, name, size):
		self.name = name
		self.size = size
		self.children = {}

def add_node(current, pathlist, size):
	assert len(pathlist) >= 1
	for token in pathlist:
		if token not in current.children:
			current.children[token] = Node(token, None)
		current = current.children[token]
	current.size = size

def fix(token):
	if not token:
		return "/"
	return token

def parse_du(f):
	root = Node("_root_", None)
	for line in f:
		line = line.strip()
		if not line:
			continue
		size, path = line.strip().split('\t')
		size = int(size)
		pathlist = [ fix(token) for token in path.split('/') ]
		add_node(root, pathlist, size)
	return root

def nodesize(node):
	if node.size is not None:
		return node.size
	return sum(nodesize(child) for child in node.children.values())

def generate_node_js(node, depth):
	yield Templates.node_header % {
		"name": node.name.replace("'", "\\'"),
		"size": nodesize(node),
	}
	
	for child in sorted(node.children.values(), key=operator.attrgetter("size"), reverse=True):
		for line in generate_node_js(child, depth + 1):
			yield line
		yield ",\n"
	
	yield Templates.node_footer

def isdummy(node):
	return node.size is None and len(node.children) == 1

def collapse(node):
	while isdummy(node):
		child = tuple(node.children.values())[0]
		name = "/" + child.name
		if node.name != "/":
			name = node.name + name
		node = Node(name, child.size)
		node.children = child.children
	return node

def normalize(root):
	for node in tuple(root.children.values()):
		if not isdummy(node):
			continue
		del root.children[node.name]
		node = collapse(node)
		root.children[node.name] = node
	return root

def generate_js(root):
	yield Templates.header
	
	for line in generate_node_js(root, 0):
		yield line
	
	yield Templates.footer

def output(fout, stream):
	for line in stream:
		fout.write(line)

def load_data():
	if len(sys.argv) == 1:
		return parse_du(sys.stdin)
	with open(sys.argv[1]) as f:
		return parse_du(f)

if __name__ == '__main__':
	output(sys.stdout, generate_js(normalize(load_data())))

